#!/usr/bin/python
# -*- coding: UTF-8 -*-

import os
import struct
import time
import select
import socket
import logging,requests
import pyperclip

from threading import Thread



class DemoLogger():
    def __init__(self,logfile):
        # 创建一个日志器
        self.logger = logging.getLogger("logger")

        # 设置日志输出的最低等级,低于当前等级则会被忽略
        self.logger.setLevel(logging.INFO)

        # 创建处理器：sh为控制台处理器，fh为文件处理器
        sh = logging.StreamHandler()

        # 创建处理器：sh为控制台处理器，fh为文件处理器,log_file为日志存放的文件夹

        fh = logging.FileHandler(logfile, encoding="UTF-8")

        # 创建格式器,并将sh，fh设置对应的格式
        formator = logging.Formatter(fmt="%(asctime)s %(levelname)s %(message)s",
                                     datefmt="%Y-%m-%d %X")
        sh.setFormatter(formator)
        fh.setFormatter(formator)

        # 将处理器，添加至日志器中
        self.logger.addHandler(sh)
        self.logger.addHandler(fh)

log_file = './'+time.strftime("%Y-%m-%d",time.localtime())+'.log'
log = DemoLogger(log_file).logger


ICMP_ECHO_REQUEST = 8  # ICMP type code for echo request messages
ICMP_ECHO_REPLY = 0  # ICMP type code for echo reply messages
ICMP_Type_Unreachable = 11  # unacceptable host
ICMP_Type_Overtime = 3  # request overtime
ID = 0  # ID of icmp_header
SEQUENCE = 0  # sequence of ping_request_msg


def checksum(strings):
    csum = 0
    countTo = (len(strings) / 2) * 2
    count = 0
    while count < countTo:
        thisVal = strings[count + 1] * 256 + strings[count]
        csum = csum + thisVal
        csum = csum & 0xffffffff
        count = count + 2
    if countTo < len(strings):
        csum = csum + strings[len(strings) - 1]
        csum = csum & 0xffffffff
    csum = (csum >> 16) + (csum & 0xffff)
    csum = csum + (csum >> 16)
    answer = ~csum
    answer = answer & 0xffff
    answer = answer >> 8 | (answer << 8 & 0xff00)
    return answer


def receiveOnePing(icmpSocket, ID, timeout):
    # 1. Wait for the socket to receive a reply
    timeBeginReceive = time.time()
    whatReady = select.select([icmpSocket], [], [], timeout)
    timeInRecev = time.time() - timeBeginReceive
    if not whatReady[0]:
        return -1
    timeReceived = time.time()
    # 2. Once received, record time of receipt, otherwise, handle a timeout
    recPacket, addr = icmpSocket.recvfrom(1024)
    # 3. Compare the time of receipt to time of sending, producing the total network delay
    byte_in_double = struct.calcsize("!d")
    timeSent = struct.unpack("!d", recPacket[28: 28 + byte_in_double])[0]
    totalDelay = timeReceived - timeSent
    # 4. Unpack the packet header for useful information, including the ID
    rec_header = recPacket[20:28]
    replyType, replyCode, replyCkecksum, replyId, replySequence = struct.unpack('!bbHHh', rec_header)
    # 5. Check that the ID matches between the request and reply
    if ID == replyId and replyType == ICMP_ECHO_REPLY:
        # 6. Return total network delay
        return totalDelay
    elif timeInRecev > timeout or replyType == ICMP_Type_Overtime:
        return -3  # ttl overtime/timeout
    elif replyType == ICMP_Type_Unreachable:
        return -11  # unreachable
    else:
        return -1


def sendOnePing(icmpSocket, destinationAddress, ID):
    icmp_checksum = 0
    # 1. Build ICMP header
    icmp_header = struct.pack('!bbHHh', ICMP_ECHO_REQUEST, 0, icmp_checksum, ID, SEQUENCE)
    time_send = struct.pack('!d', time.time())
    # 2. Checksum ICMP packet using given function
    icmp_checksum = checksum(icmp_header + time_send)
    # 3. Insert checksum into packet
    icmp_header = struct.pack('!bbHHh', ICMP_ECHO_REQUEST, 0, icmp_checksum, ID, SEQUENCE)
    # 4. Send packet using socket
    icmp_packet = icmp_header + time_send
    icmpSocket.sendto(icmp_packet, (destinationAddress, 80))


def doOnePing(destinationAddress, timeout):
    try:
    
        # 1. Create ICMP socket
        icmpName = socket.getprotobyname('icmp')
        icmp_Socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmpName)
        # 2. Call sendOnePing function
        sendOnePing(icmp_Socket, destinationAddress, ID)
        # 3. Call receiveOnePing function
        totalDelay = receiveOnePing(icmp_Socket, ID, timeout)
        # 4. Close ICMP socket
        icmp_Socket.close()
        # 5. Return total network delay
        return totalDelay
    except :
        return 0


def ping(host, count, timeout):
    send = 0
    lost = 0
    receive = 0
    maxTime = 0
    minTime = 1000
    sumTime = 0
    # 1. Look up hostname, resolving it to an IP address
    desIp = socket.gethostbyname(host)
    global ID
    ID = os.getpid()

    for i in range(0,count):

        global SEQUENCE
        SEQUENCE = i
        delay = doOnePing(desIp, timeout) * 1000

        send += 1
        if delay > 0:
            receive += 1
            if maxTime < delay:
                maxTime = delay
            if minTime > delay:
                minTime = delay
            sumTime += delay

            log.info("\033[1;29m Receive from: {0}, delay ={1} ms {2} \033[0m".format(desIp,round(delay),i))
        else:
            lost += 1

            if delay == -11:
                # type = 11, target unreachable
                log.info("Target net/host/port/protocol is unreachable.")
            elif delay == -3:
                # type = 3, ttl overtime
                log.info("Fail to connect. Request overtime.code -3")
            else:
                # otherwise, overtime
                log.info("检测失败，请求超时...")
            # continue
        time.sleep(1)
    data ={}
    if receive != 0:
        avgTime = sumTime / receive
        recvRate = receive / send * 100.0
        data = {'send':send,'recv':receive,'lost':lost,'succ':int(recvRate),
                'max':int(maxTime),'min':int(minTime),'avg':int(avgTime),'dd':round(maxTime-minTime) }

        log.info("\033[1;34m 发包 {0} 接收 {1} 丢失 {2} 成功 {3}% 最小延时 {4}ms 最大延时 {5}ms 平均延时 {6}ms 抖动 {7}ms \033[0m"
                 .format(data['send'],data['recv'],data['lost'],data['succ'],
                         data['min'],data['max'],data['avg'],data['dd']))
        if data['dd'] > arg.ddTime or data['max'] > arg.maxTime:
            sendWeChat(str(desIp) +' 网络抖动通知',str(data))
    else:
        log.info("\033[1;34m 发包 {0} 接收 {1} 丢失 {2} \033[0m".format(send, receive, lost))


def sendWeChat(text,desp):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36 QIHU 360SE'
    }
    url = 'https://iyuu.cn/IYUU13283T61f8c911e33ca47405b1bb1d14af3e9b26534a68.send'
    param = {'text': text,'desp': desp}
    req = requests.get(url,headers=headers,params=param)
    if req.status_code == 200 :
        print('发送成功',req.text)
    else:
        print('发送失败')

def fromClipGetIp():
    global hostName
    while True:
        clip = pyperclip.paste()
        cnt = clip.count('.')
        if cnt == 3:
            print('检测到IP地址',clip.strip())
            hostName = clip.strip()
            pyperclip.copy('')
        time.sleep(2)

if __name__ == '__main__':
    import argparse

    args = argparse.ArgumentParser(description='pingWatch useing readme')
    args.add_argument('--ddTime', type=int, default=100, help='抖动超时通知 时间毫秒')
    args.add_argument('--maxTime', type=int, default=500, help='最大延迟通知 时间毫秒')
    args.add_argument('--timeOut', type=int, default=5, help='ping超时 时间秒')
    args.add_argument('--count', type=int, default=10, help='ping周期包统计 默认10个包')
    arg = args.parse_args()
    hostName = ''
    t1 = Thread(target=fromClipGetIp)
    t1.start()
    log.info('子线程开始运行...')

    print('-------------------')
    print('version 1.0.3')
    print('date:2023-8-8 7:24')
    print('修复doOnePing函数异常跳出循环')
    print('新增日志颜色')
    print('待实现功能 : 微信推送次数限制')
    print('-------------------')
    now = time.localtime(time.time())
    ctime = time.strftime("%Y-%m-%d %H:%M:%S", now)

    fail = 0
    log.info('开始检测-----------------------------------------')


    while True:
        try:

            fail = 0
            if hostName != '':
                ping(hostName,arg.count ,arg.timeOut )
            pingcnt += 1
            # log.info('------------{0}-----------'.format(pingcnt))
            ping(hostName, count, timeout)
        except Exception as e:
            fail += 1
            if fail >= arg.count:
                fail = 0
                log.error('\033[1;31m -------------------网络不可达---------------------- \033[0m')
                sendWeChat(hostName + '网络检测异常','网络丢包超过'.format(arg.count))
                log.error('-------------------网络不可达----------------------'+str(fail))
